package chloroform

import exec._
import javax.servlet.http.{HttpServletResponse, HttpServletRequest, HttpServlet}
import scaladuct.Starter
import javax.servlet.ServletException
import argo.jdom.{JsonNodeFactories, JsonStringNode, JsonNode}

/**
 * /exec - invoking the context instance
 * /exec/entryName - invoking the specific instance created by entry entryName
 * /exec/entryName?parameter0=abc&parameter1=true - invoking the specific instance created by entry entryName
 * with parameters
 *
 * User: zslajchrt
 * Date: 13.4.11
 * Time: 19:56
 */


class ExecutorServlet extends HttpServlet {

  private val executorChainFactory =
    new DefaultInstanceExecutorChainFactory(List(
      ServiceFactoryExecutor,
      InputResourceExecutor,
      OutputResourceExecutor,
      RunnableExecutor,
      PlainObjectExecutor
    ))

  override def doGet(req: HttpServletRequest, resp: HttpServletResponse) = {
    handleRequest(req, resp)
  }

  override def doPost(req: HttpServletRequest, resp: HttpServletResponse) = {
    handleRequest(req, resp)
  }

  def handleRequest(req: HttpServletRequest, resp: HttpServletResponse) = {
    val entryNameInPath = req.getPathInfo
    val (starter, instance): (Starter[Any], Any) = if (entryNameInPath == null || entryNameInPath == "" ||
      entryNameInPath == "/") {
      StarterConfigMessageHandler.getContextInstance(req.getSession)
    } else {
      val entryName = if (entryNameInPath.startsWith("/"))
        entryNameInPath.substring(1)
      else
        entryNameInPath
      instantiate(entryName, req)
    }

    try {
      execute(instance, req, resp)
    } finally {
      if (starter != null)
        starter.destroy(instance)
    }

  }

  def instantiate(entryName: String, req: HttpServletRequest): (Starter[Any], Any) = {
    val register: ConfigPageRegister = ConfigPageRegisterLookup.getConfigPageRegister(req)
    val params = resolveParamCookie(req)
    val starter: Starter[Any] = register.getStarter(entryName, additionalCookies = params)
    (starter, starter.newInstance)
  }

  def resolveParamCookie(req: HttpServletRequest): List[ProjectCookie] = {
    val query = req.getQueryString
    if (query == null || query == "") {
      Nil
    } else {
      val paramNames = req.getParameterNames
      val propMap = new java.util.HashMap[JsonStringNode, JsonNode]()
      while (paramNames.hasMoreElements) {
        val paramName = paramNames.nextElement
        propMap.put(JsonNodeFactories.aJsonString(paramName), createParamJsonNode(req.getParameter(paramName)))
      }
      val paramCookie: ProjectCookie = new ProjectCookie(null, propMap)
      List(paramCookie)
    }
  }

  def createParamJsonNode(paramValueAsString: String): JsonNode = {
    paramValueAsString match {
      case null => JsonNodeFactories.aJsonNull
      case "true" => JsonNodeFactories.aJsonTrue
      case "false" => JsonNodeFactories.aJsonFalse
      case _ => {
        try {
          JsonNodeFactories.aJsonNumber(paramValueAsString)
        }
        catch {
          case iae: IllegalArgumentException => JsonNodeFactories.aJsonString(paramValueAsString)
        }
      }
    }
  }

  def execute(instance: Any, req: HttpServletRequest, resp: HttpServletResponse) {
    val chain = executorChainFactory.createChain(instance, req, resp)
    chain.execute
  }

}

trait InstanceExecutor {
  def execute(instance: Any, context: InstanceExecutorContext)
}

trait InstanceExecutorChainFactory {
  def createChain(instance: Any, req: HttpServletRequest, resp: HttpServletResponse): InstanceExecutorChain
}

trait InstanceExecutorChain {
  def execute(): Unit

  def createAnotherChain(instance: Any): InstanceExecutorChain
}

class InstanceExecutorContext(val chainFactory: InstanceExecutorChainFactory,
                              val chain: InstanceExecutorChain,
                              val request: HttpServletRequest,
                              val response: HttpServletResponse)

// default implementation

class DefaultInstanceExecutorChainFactory(executors: List[InstanceExecutor]) extends InstanceExecutorChainFactory {

  def createChain(instance: Any, req: HttpServletRequest, resp: HttpServletResponse) =
    new DefaultInstanceExecutorChain(instance, executors, this, req, resp)

}

class DefaultInstanceExecutorChain(instance: Any,
                                   private[this] var executors: List[InstanceExecutor],
                                   chainFactory: InstanceExecutorChainFactory,
                                   req: HttpServletRequest,
                                   resp: HttpServletResponse) extends InstanceExecutorChain {

  val context = new InstanceExecutorContext(chainFactory, this, req, resp)

  def createAnotherChain(anotherInstance: Any) =
    context.chainFactory.createChain(anotherInstance, context.request, context.response)

  def execute() = {
    require(!executors.isEmpty, "No executor found for instance " + instance)
    val headExecutor: InstanceExecutor = executors.head
    executors = executors.tail
    headExecutor.execute(instance, context)
  }
}